#pragma once

#include <cassert>
#include <vector>
#include <deque>
#include <iostream>

namespace ClassTemplate
{

    /**
     * 偏特化与特例化
     * 1、类模板的声明
     * 2、特例化：如声明 template<typename T> class A; 可以特例化为 template<> class A<std::string>;
     * 3、偏特化：如声明 template<typename T> class A; 可以特例化为 template<typename T> class A<T*>;
     * 4、类模板的默认参数
     * 5、类模板的别名
     * 6、类模板的参数推导（C++17）
     * 7、聚合模板
     */

    /**
     * 1、类模板的声明
     */
    template <typename T>
    class Stack;

    template <typename T>
    std::ostream &operator<<(std::ostream &os, Stack<T> &stack)
    {
        stack.printOn(os);
        return os;
    }

    template <typename T>
    class Stack
    {
        // 使用友元函数进行元素的打印
        // 1、第一种写法，注意函数中需要指定operator<<<T>！，相当于模板特例化
        friend std::ostream &operator<< <T>(std::ostream &os, Stack<T> &stack);
        // 2、第二种写法，相当于将函数模板作为友元
        template <typename U>
        friend std::ostream &operator<<(std::ostream &os, Stack<U> &stack);

    public:
        Stack() = default;
        Stack(T elem) : elems({elem})
        {
            std::cout << "Stack<T> init" << std::endl;
        }
        void push(T const &elem);
        void pop();
        T const &top() const;
        bool empty()
        {
            return elems.empty();
        }

    private:
        void printOn(std::ostream &os) const
        {
            for (auto &&elem : elems)
            {
                os << elem << " ";
            }
        }

    private:
        std::vector<T> elems;
    };

    template <typename T>
    inline void Stack<T>::push(T const &elem)
    {
        elems.emplace_back(elem);
    }

    template <typename T>
    inline void Stack<T>::pop()
    {
        assert(!elems.empty());
        elems.pop_back();
    }

    template <typename T>
    inline T const &Stack<T>::top() const
    {
        assert(!elems.empty());
        return elems.back();
    }

    /**
     * 2、类模板的特例化
     */
    template <>
    class Stack<std::string>
    {
        template <typename T>
        friend std::ostream &operator<<(std::ostream &os, Stack<T> &stack);

    public:
        Stack()
        {
            std::cout << "Stack<std::string> init" << std::endl;
        }

        Stack(const std::string &elem) : elems({elem})
        {
            std::cout << "Stack<std::string> init" << std::endl;
        }

        void push(std::string const &elem);
        void pop();
        std::string top() const;
        bool empty()
        {
            return elems.empty();
        }

    private:
        void printOn(std::ostream &os) const
        {
            for (auto &&elem : elems)
            {
                os << elem << " ";
            }
        }

    private:
        std::deque<std::string> elems;
    };

    inline void Stack<std::string>::push(std::string const &elem)
    {
        elems.emplace_back(elem);
    }

    inline void Stack<std::string>::pop()
    {
        assert(!elems.empty());
        elems.pop_back();
    }

    inline std::string Stack<std::string>::top() const
    {
        assert(!elems.empty());
        return elems.back();
    }

    /**
     * 3、类模板的偏特化
     */
    template <typename T>
    class Stack<T *>
    {
        template <typename U>
        friend std::ostream &operator<<(std::ostream &os, Stack<U> &stack);

    public:
        Stack()
        {
            std::cout << "Stack<T*> init" << std::endl;
        }
        void push(T *p);
        T *pop();
        T *top() const;

    private:
        void printOn(std::ostream &os)
        {
            for (T *elemPointer : elemPointers)
            {
                std::cout << *elemPointer << " ";
            }
        }

    private:
        std::vector<T *> elemPointers;
    };

    template <typename T>
    inline void Stack<T *>::push(T *p)
    {
        elemPointers.emplace_back(p);
    }

    template <typename T>
    inline T *Stack<T *>::pop()
    {
        assert(!elemPointers.empty());
        T *lastPointer = elemPointers.back();
        elemPointers.pop_back();
        return lastPointer;
    }

    template <typename T>
    inline T *Stack<T *>::top() const
    {
        assert(!elemPointers.empty());
        return elemPointers.back();
    }
    // 多个参数的偏特化
    template <typename T1, typename T2>
    class MyClass
    {
    };

    template <typename T>
    class MyClass<int, T>
    {
    };

    template <typename T>
    class MyClass<T, float>
    {
    };

    template <typename T>
    class MyClass<T, T>
    {
    };

    template <typename T1, typename T2>
    class MyClass<T1 *, T2 *>
    {
    };

    // 如果传入 MyClass<int*,int*> ，既可以匹配<T,T>,，也可以匹配<T1*,T2*>，会导致歧义。可以用以下方式解决：
    template <typename T>
    class MyClass<T *, T *>
    {
    };

    /**
     * 4、类模板的默认参数
     *      类似标准库中：
     *      template<typename _Tp, typename _Sequence = vector<_Tp>,
     *          typename _Compare  = less<typename _Sequence::value_type> >
     *      class priority_queue;
     *      priority_queue 的默认容器为 vector<_Tp>，比较器为 less<typename _Sequence::value_type>
     */

    template <typename T, typename Cont = std::deque<T>>
    class ContStack
    {
    public:
        // ...
    private:
        Cont elems;
    };

    /**
     * 5、类型别名
     * 可以使用 typedef 或者 using 关键字进行定义
     */
    using IntStack = Stack<int>;

    template <typename T>
    using DequeStack = ContStack<T, std::deque<T>>;

    /**
     * 6、类模板的参数推导（C++17标准）
     *  可以不显示的指定<>中的类型，而是通过传入的参数判断
     *  对于字符串，需要进行特殊的处理，否则传入 "hello world"，T会推导为 char const[11] 的类型。导致出现错误。
     *      对于字符串，需要进行显示的类型推导：
     *      Stack(const char*)->Stack<std::string>; // 添加在跟Stack相同命名空间中！
     */
    /*
    class Stack{
        // 使用友元函数进行元素的打印
        // 1、第一种写法，注意函数中需要指定operator<<<T>！，相当于模板特例化
        friend std::ostream& operator<<<T>(std::ostream& os, Stack<T>& stack);
        // 2、第二种写法，相当于将函数模板作为友元
        template<typename U>
        friend std::ostream& operator<<(std::ostream& os, Stack<U>& stack);
    public:
        // 声明以下两个构造函数，可以使用传入 T elem ，进行构造
        Stack() = default;
        Stack(T elem):elems({elem}){
            std::cout << "Stack<T> init"<<std::endl;
        }
    };

    // 调用，可以不指定<>中的类型：
    Stack intStack = 10;
    */

    Stack(const char *) -> Stack<std::string>; // 指定 const char* 会被推导成 std::string (since c++17)
    // 调用 Stack stringStack = "hello world!";

    /**
     * 7、模板聚合
     * 聚合类 (不由用户提供、显式或继承的构造函数的类/结构，没有 private 或 protected 的非静态数据成员，
     *      没有虚函数，也没有 virtual、private 或 protected 基类) 也可以是模板。
     */
    template <typename T>
    struct ValueWithComment
    {
        T value;
        std::string comment;
    };
    // c++17中可以为聚合类模板提供推导策略
    ValueWithComment(const char *, const char *) -> ValueWithComment<std::string>;
    
};
